Categories
Node.js Best Practices

Node.js Best Practices — Going to Production

Spread the love

Node.js is a popular runtime to write apps for. These apps are often production quality apps that are used by many people. To make maintaining them easier, we’ve to set some guidelines for people to follow.

In this article, we’ll look at what we should with our Node app before and after going to production.

Use Production-Like Environment for E2E Testing

Running end to end tests in a production-like environment ensures that we won’t run into different issues in productions that we catch beforehand.

Also, we should run our tests in a database with clean data so that we can repeat our tests.

Refactor Regularly Using Static Analysis Tools

Before putting our code to production, we should refactor our code so that it runs quickly in production. The automated tests will help us to make sure that refactoring won’t break any existing functionality.

Poor code quality will create more bugs and performance issues that are hard to fix.

Carefully Choose Our CI Platform

Jenkins and CircleCI are popular platforms for continuous integration. Having a CI (continuous integration) pipeline lets us run tests and deploy automatically in the background rather than running everything manually. It frees us to do other kinds of work and frees us from manually managing the infrastructure.

We’ve to choose carefully since migration from one to the other will be a pain.

Monitoring Our App

We should monitor our app so that our app runs properly and not taking up too many resources. To do this, we can use monitoring tools and add health check endpoints to check if our app is running.

This way, we don’t have to let our customers tell us that our app has failed.

Increase Transparency Using Smart Logging

Logging lets us troubleshoot problems easily by spotting the activities in the lo that may be causing problems. Most logging platforms can control how logs are collected, stored, and analyzed to ensure that it’s only storing the data that we want.

Delegate Anything Possible to a Reverse Proxy

If something can be done with a reverse proxy, then they don’t belong in our app. CPU intensive tasks like SSL, Gzipping, termination should all be done on a reverse proxy to take the load off our app.

This is especially important for Node apps since it only runs on one thread, so we don’t want to tie it up by making it do infrastructure-related tasks that belongs to the reverse proxy.

Lock Dependencies

We should lock our app’s dependencies so that they won’t change versions across environments. Nowadays, this should be done automatically since npm install generates a package-lock.json if it doesn’t exist. If it does exist, then npm install will use the versions in the file to install the dependencies.

If it doesn’t exist in our repo or if we use fine-grained control of how the versions are locked, we can run npm shrinkwrap . This command repurposes package-lock.json into a publishable npm-shrinkwrap.json or creates a new one.

It takes precedence over package-lock.json .

Guard Process Uptime Using the Right Tool

Our app must be restarted when it fails. We can use Forever or PM2 to watch our app and restart it when it crashes. If we have a cluster, then we also have to manage that.

Utilize All CPU Cores

We should use all the CPU cores to run our app with the fastest performance possible. A CPU core is useless if it’s left idling. If that’s the case, we should replicate the Node processes and utilize all CPUs. For small apps, we may use Node Cluster or PM2. Otherwise, we may use a Docker cluster like ECS.

Create a ‘Maintenance Endpoint’

We can use this to securely expose diagnostic information from our app without logging into the server. Some information are just easier to get using code.

In Node, we can use the os module to expose information about our server. For example, we can get the platform for our app as follows and return it via an endpoint:

const express = require('express');
const bodyParser = require('body-parser');
const os = require('os');

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.get('/', (req, res, next) => {
  res.send(os.platform());
});

app.listen(3000, () => console.log('server started'));
module.exports = app;

Of course, in a production app, this should be secured with authentication.

Conclusion

Before going to production, we should have a set of end to end tests that run in a clean production-like environment. The data should be reset each test run so that they actually run properly. This also helps with testing after refactoring.

We should make sure that we have automated deploy to free our time for other tasks.

Also, make sure that our server’s CPU cores are all utilized.

Finally, we may want to create a secure maintenance endpoint to expose some information to us without logging into the server.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *